package mmc.server;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import mmc.Constants;
import mmc.EngineObject;
import mmc.MovableEngineObject;
import mmc.Settings;
import mmc.cell.Block;
import mmc.cell.BlockIterator;
import mmc.cell.ServerCell;
import mmc.cell.ServerCellManager;
import mmc.cell.ServerCellProvider;
import mmc.objects.Mark;
import mmc.objects.Mob;
import mmc.objects.Sun;
import mmc.physics.PhysicsEngine;

public class Scene {
	private Server server;
	private ServerCellProvider cellProvider;
	private ServerCellManager cellManager;
	private PhysicsEngine physics;
	
	public Map<Integer, EngineObject> objects = new HashMap<Integer, EngineObject>();
	private List<Player> players = new ArrayList<Player>();
	
	private Set<ServerCell> dirtyCells = new HashSet<ServerCell>();
	private List<EngineObject> objectsToAdd = new Vector<EngineObject>();
	private List<EngineObject> objectsToRemove = new Vector<EngineObject>();
	private EngineObject spawnPoint;
	
	public Scene(Server server){
		this.server = server;
		
		spawnPoint = new Mark(Constants.CHUNK_BLOCKS_HALF+Constants.CELL_SIZE_HALF,2,Constants.CHUNK_BLOCKS_HALF+Constants.CELL_SIZE_HALF);
		cellProvider = server.getCellProvider();
		cellManager = cellProvider.getCellManager();
		
		addObject(new Sun());
		addObject(new Mob(spawnPoint.x,spawnPoint.y,spawnPoint.z));
	}
	
	public void removeObject(EngineObject o){
		if(o instanceof Player){
			server.broadcast(((Player) o).getName()+" quit");
		}
		
		synchronized(objectsToRemove){
			objectsToRemove.add(o);
		}
	}
	
	public void addObject(EngineObject o){
		if(o instanceof Player){
			server.broadcast(o.toString()+" connected");
		}
		
		synchronized(objectsToAdd){
			objectsToAdd.add(o);
		}
	}
	
	public void update(float dt) {
		synchronized(objectsToAdd){
			for(EngineObject e : objectsToAdd){
				objects.put(e.getId(), e);
				if(e instanceof Player)
					players.add((Player) e);
			}
			objectsToAdd.clear();
		}
		
		synchronized(objectsToRemove){
			for(EngineObject e : objectsToRemove){
				objects.remove(e.getId());
				if(e instanceof Player){
					players.remove(e);
					((Player)e).stop();
				}
			}
			objectsToRemove.clear();
		}
		
		float dtl = dt;
		while((dt = (dtl>Settings.PHYSICS_MINIMAL_TIME_STEP?Settings.PHYSICS_MINIMAL_TIME_STEP:dtl)) > 0){
			dtl -= dt;
			
			//update objects
			for(EngineObject o : objects.values()){
				o.update(server, dt);
				if(o instanceof MovableEngineObject)
					physics.update((MovableEngineObject) o, dt);
			}
		}
		
		cellManager.update(server, dt, dirtyCells);
		
		//notify players of dirty chunks
		if(!dirtyCells.isEmpty())
			synchronized(dirtyCells){
				for(Player p : players){
					p.notifyChunksDirty(dirtyCells);
				}
				dirtyCells.clear();
			}
		
		//notify player if object in range is dirty
		for(EngineObject o : objects.values()){
			if(o.dirty){
				for(Player p : players){
					if(p.knowsAbout(o))
						p.markDirty(o);
				}
				o.dirty = false;
			}
		}
		
		//calculate which objects are in player range and inform players
		for(Player p : players){
			if(p.wantsUpdate()){
				List<EngineObject> updateList = new ArrayList<EngineObject>();
				
				for(EngineObject o : objects.values()){
					if(o.inRange(p, p.viewDistance)){
						updateList.add(o);
					}
				}
				
				p.notifySendUpdate(updateList);
			}
		}
	}
	
	public void markCellsAsDirty(Collection<ServerCell> chunks){
		synchronized(dirtyCells){
			dirtyCells.addAll(chunks);			
		}
	}
	
	public void doDmgAOE(EngineObject projectile, float range, float damage){
		BlockIterator toDestroy = cellManager.getBlocksNearObject(projectile, range);
		
		while(toDestroy.next()){
			toDestroy.setBlockId(Block.AIR_DARKNESS);
		}
		
		List<ServerCell> ac = toDestroy.getAffectedChunks();
		
		for(ServerCell c : ac)
			c.compress();
		
		markCellsAsDirty(ac);
		cellProvider.getCellStorage().markCellsAsDirty(ac);
		
		for(EngineObject o : objects.values()){
			if(o!=projectile && projectile.inRange(o, range)){
				o.takeDamage((int)damage);			//TODO
			}
		}
	}
	
	public EngineObject getSpawnPoint(){
		return spawnPoint;
	}

	public void shutdown() {
		System.out.println("Shutting down scene...");
		
		for(Player p : players){
			p.stop();
			p.getClient().disconnect();
		}
	}
	
	public void setPhysics(PhysicsEngine e){
		physics = e;
	}
	
	public Collection<EngineObject> getObjects(){
		return objects.values();
	}

	public List<Player> getPlayers() {
		return players;
	}
	
	public ServerCellManager getCellManager(){
		return cellManager;
	}
}
